﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using AmiEmuLib.AmigaConfiguration.ChipsetConfig;
using AmiEmuLib.AmigaConfiguration.CoreConfig;
using AmiEmuLib.AmigaConfiguration.DisplayConfig;
using AmiEmuLib.AmigaConfiguration.JoyportsConfig;
using AmiEmuLib.AmigaConfiguration.MountedDevicesConfig;
using AmiEmuLib.AmigaConfiguration.MemoryConfig;
using AmiEmuLib.AmigaConfiguration.InputConfig;
using AmiEmuLib.AmigaConfiguration.FloppiesConfig;
using AmiEmuLib.Configuration.EventBindingConfig;
using AmiEmuLib.Configuration;
using AmiEmuLib.Configuration.FloppySoundConfig;
using AmiEmuLib.Configuration.FiltersConfig;
using AmiEmuLib.Configuration.KnownRomConfig;
using log4net;
using System.Xml;
using AmiEmuLib.Host.HostInput;
using TomanuExtensions;
using TomanuExtensions.Utils;
using AmiEmuLib.AmigaConfiguration.AudioConfig;

namespace AmiEmuLib.AmigaConfiguration
{
    [DebuggerDisplay("AmigaModel = {AmigaModel}, Description = {Description}")]
    public class AmigaConfig : XmlBase
    {
        /// <summary>
        /// Logger.
        /// </summary>
        /// TODO: stowrzyc klase loggerow statycznych, dodac config logowania
        private static readonly ILog Logger = 
            LogManager.GetLogger(typeof(AmigaConfig).GetParentFullName());

        /// <summary>
        /// AmiEmu version.
        /// </summary>
        public string Version;

        /// <summary>
        /// Previous load/save file.
        /// </summary>
        public string FileName = "";

        /// <summary>
        /// Amiga model.
        /// </summary>
        public AmigaModel AmigaModel;

        /// <summary>
        /// Short descriptions.
        /// </summary>
        public string Description;

        /// <summary>
        /// Audio configuration.
        /// </summary>
        public Audio Audio;
        
        /// <summary>
        /// Chipset configuration.
        /// </summary>
        public Chipset Chipset;

        /// <summary>
        /// CPU condifguration.
        /// </summary>
        public CPU CPU;

        /// <summary>
        /// FPU configuration.
        /// </summary>
        public FPU FPU;

        /// <summary>
        /// MMU configuration
        /// </summary>
        public MMU MMU;

        /// <summary>
        /// Memory configuration.
        /// </summary>
        public Memory Memory;

        /// <summary>
        /// Mounted devices configuration.
        /// </summary>
        public MountedDevices MountedDevices;

        /// <summary>
        /// Host display configuration
        /// </summary>
        public Display Display;

        /// <summary>
        /// Floppies configuration.
        /// </summary>
        public Floppies Floppies;

        /// <summary>
        /// Joyport 1
        /// </summary>
        public Joyport1 Joyport1;

        /// <summary>
        /// Joyport 2
        /// </summary>
        public Joyport2 Joyport2;

        /// <summary>
        /// Parallel joystick 1
        /// </summary>
        public ParallelJoystick1 ParallelJoystick1;

        /// <summary>
        /// Parallel joystick 2
        /// </summary>
        public ParallelJoystick2 ParallelJoystick2;

        /// <summary>
        /// Input maps configurations.
        /// </summary>
        public Input Input;

        /// <summary>
        /// Constructor. 
        /// </summary>
        public AmigaConfig()
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_file"></param>
        public AmigaConfig(string a_file_name)
        {
            Load(a_file_name);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_config"></param>
        public AmigaConfig(AmigaConfig a_config)
            : base(a_config)
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_reader"></param>
        public AmigaConfig(XmlReader a_reader)
            : base(a_reader)
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_stream"></param>
        public AmigaConfig(Stream a_stream)
        {
            Load(a_stream);
        }

        protected override void Load(string a_file_name)
        {
            base.Load(a_file_name);
            FileName = a_file_name;
        }

        public override void Save(string a_file_name = "")
        {
            if (a_file_name == "")
                a_file_name = FileName;

            base.Save(a_file_name);
        }

        protected override void ReadXml(XmlReader a_reader)
        {
            Version = a_reader.GetAttribute("Version");
            AmigaModel = a_reader.GetAttributeEnum<AmigaModel>("AmigaModel");

            a_reader.ReadStartElement(GetType().Name);

            Description = a_reader.ReadElementContentAsString("Description");

            Audio = new Audio(a_reader);
            Chipset = new Chipset(a_reader);
            CPU = new CPU(a_reader);
            FPU = new FPU(a_reader);
            MMU = new MMU(a_reader);
            Memory = new Memory(a_reader);
            MountedDevices = new MountedDevices(a_reader);
            Display = new Display(a_reader);
            Floppies = new Floppies(a_reader);

            Joyport1 = new Joyport1(a_reader);
            Joyport2 = new Joyport2(a_reader);

            ParallelJoystick1 = new ParallelJoystick1(a_reader);
            ParallelJoystick2 = new ParallelJoystick2(a_reader);

            Input = new Input(a_reader);

            a_reader.ReadEndElement();

            Check();
        }

        public override void WriteXml(XmlWriter a_writer)
        {
            Check();

            a_writer.WriteStartElement(GetType().Name);

            a_writer.WriteAttribute("Version", Version);
            a_writer.WriteAttribute<AmigaModel>("AmigaModel", AmigaModel);
            a_writer.WriteElement("Description", Description);

            Audio.WriteXml(a_writer);
            Chipset.WriteXml(a_writer);
            CPU.WriteXml(a_writer);
            FPU.WriteXml(a_writer);
            MMU.WriteXml(a_writer);
            Memory.WriteXml(a_writer);
            MountedDevices.WriteXml(a_writer);
            Display.WriteXml(a_writer);
            Floppies.WriteXml(a_writer);

            Joyport1.WriteXml(a_writer);
            Joyport2.WriteXml(a_writer);

            ParallelJoystick1.WriteXml(a_writer);
            ParallelJoystick2.WriteXml(a_writer);

            Input.WriteXml(a_writer);
            
            a_writer.WriteEndElement();
        }

        /// <summary>
        /// In future we should use this information to avoid produce in GUI invalid configs.
        /// </summary>
        [Conditional("DEBUG")]
        public void Check()
        {
            Debug.Assert(Enum.IsDefined(typeof(AmigaModel), AmigaModel));
            Debug.Assert(Enum.IsDefined(typeof(CPUModel), CPU.Model));
            Debug.Assert(Enum.IsDefined(typeof(FPUModel), FPU.Model));
            Debug.Assert(Enum.IsDefined(typeof(MMUModel), MMU.Model));
            Debug.Assert(Enum.IsDefined(typeof(CPUSpeed), CPU.Speed));
            Debug.Assert(Enum.IsDefined(typeof(ChipsetType), Chipset.Type));
            Debug.Assert(Enum.IsDefined(typeof(CIAATODSource), Chipset.CIAATODSource));
            Debug.Assert(Enum.IsDefined(typeof(RealTimeClockType), Chipset.RealTimeClock));
            Debug.Assert(Enum.IsDefined(typeof(AmigaAudioFilter), Audio.Filter));
            Debug.Assert(Enum.IsDefined(typeof(TVMode), Chipset.TVMode));
            Debug.Assert(Enum.IsDefined(typeof(BufferingMode), Display.Buffering));
            Debug.Assert(Enum.IsDefined(typeof(IDEType), Chipset.IDE));
            Debug.Assert(Enum.IsDefined(typeof(GaryType), Chipset.Gary));
            Debug.Assert(Enum.IsDefined(typeof(RamseyType), Chipset.Ramsey));
            Debug.Assert(Enum.IsDefined(typeof(DMACType), Chipset.DMAC));
            Debug.Assert(Enum.IsDefined(typeof(CDTVMemoryCardType), Chipset.CDTVMemoryCard));
            Debug.Assert(Enum.IsDefined(typeof(SpriteCollisionLevel), Chipset.SpriteCollisionLevel));
            Debug.Assert(Enum.IsDefined(typeof(VSyncMode), Display.Fullscreen.VSync));
            Debug.Assert(Enum.IsDefined(typeof(FullscreenScalingMode), Display.Fullscreen.Scaling));
            Debug.Assert(Enum.IsDefined(typeof(WindowScalingMode), Display.Window.Scaling));
            Debug.Assert(Enum.IsDefined(typeof(ScaleFactor), Display.Window.ScaleFactor));
            Debug.Assert(Enum.IsDefined(typeof(ScaleFactor), Display.Fullscreen.ScaleFactor));

            Debug.Assert(CPU.Model != CPUModel.MC68010);

            if (CPU.Model == CPUModel.MC68000)
                Debug.Assert(CPU.AddressSpace24Bits);
            if (CPU.Model == CPUModel.MC68EC020)
                Debug.Assert(CPU.AddressSpace24Bits);

            if (CPU.AddressSpace24Bits)
            {
                Debug.Assert((CPU.Model == CPUModel.MC68000) ||
                             (CPU.Model == CPUModel.MC68EC020));
                Debug.Assert(!CPU.JIT.Enabled);

                Debug.Assert(FPU.Model == FPUModel.None);
                Debug.Assert(MMU.Model == MMUModel.None);

                Debug.Assert(Memory.ProcessorSlotMemorySize == 0);
                Debug.Assert(Memory.MotherboardMemorySize == 0);
                Debug.Assert(Memory.Z3MemorySize == 0);
            }

            if ((FPU.Model != FPUModel.None) && (CPU.BuildInFPU != FPUModel.None))
                Debug.Assert(CPU.BuildInFPU == FPU.Model);
            if (CPU.Model == CPUModel.MC68000)
                Debug.Assert(FPU.Model == FPUModel.None);

            if ((MMU.Model != MMUModel.None) && (CPU.BuildInMMU != MMUModel.None))
                Debug.Assert(CPU.BuildInMMU == MMU.Model);
            Debug.Assert(MMU.Model != MMUModel.MC68551);

            if (CPU.Speed == CPUSpeed.CycleExact)
            {
                Debug.Assert((CPU.Model == CPUModel.MC68000) || (CPU.Model == CPUModel.MC68EC020));
                Debug.Assert(FPU.Model == FPUModel.None);
                Debug.Assert(MMU.Model == MMUModel.None);
                Debug.Assert(CPU.JIT.Enabled == false);
                Debug.Assert(CPU.Prefetch == true);
            }

            if (CPU.Prefetch)
                Debug.Assert(!CPU.JIT.Enabled);

            if (CPU.JIT.Enabled)
            {
                Debug.Assert(CPU.JIT.CacheSize > 0);
                Debug.Assert(!CPU.AddressSpace24Bits);
                Debug.Assert(!CPU.Prefetch);
            }

            Debug.Assert(Memory.ChipSizes.Contains(Memory.ChipMemorySize));
            Debug.Assert(Memory.FastSizes.Contains(Memory.FastMemorySize));
            Debug.Assert(Memory.MotherboardMemory.Contains(Memory.ProcessorSlotMemorySize));
            Debug.Assert(Memory.MotherboardMemory.Contains(Memory.MotherboardMemorySize));
            Debug.Assert(Memory.SlowSizes.Contains(Memory.SlowMemorySize));
            Debug.Assert(Memory.Z3Memory.Contains(Memory.Z3MemorySize));

            // For compatibilty with WinUAE config.
            foreach (var md in MountedDevices.List)
            {
                Debug.Assert(md.BootPriority <= 127);
                if (md.BootPriority == -129)
                {
                    Debug.Assert(md.Mount == false);
                    Debug.Assert(md.Bootable == false);
                }
                if (md.BootPriority == -128)
                {
                    Debug.Assert(md.Mount == true);
                    Debug.Assert(md.Bootable == false);
                }

                if (!md.Bootable)
                    Debug.Assert(md.BootPriority < -127);
                if (!md.Mount)
                    Debug.Assert(md.BootPriority == -129);

                Debug.Assert(md.BootPriority >= -129);

                if (md is Hardfile)
                {
                    Hardfile hf = md as Hardfile;
                    Debug.Assert(Enum.IsDefined(typeof(MountedDeviceController), hf.Controller));
                }
            }

            if (CPU.Speed == CPUSpeed.CycleExact)
                Debug.Assert(Display.RenderEveryNFrame == 1);

            Debug.Assert(Floppies.List.Count == 4);
            foreach (var floppy in Floppies.List)
            {
                Debug.Assert(Enum.IsDefined(typeof(FloppySpeed), floppy.Speed));
                Debug.Assert(NumberExtensions.InRange(floppy.Volume, 0, 100));
                Enum.IsDefined(typeof(FloppyType), floppy.Type);
                if (floppy.Sounds != "")
                    Debug.Assert(FloppySoundsList.List.Any(el => el.Name == floppy.Sounds));
            }

            foreach (var joy in new Joyport [] { Joyport1, Joyport2 })
            {
                Debug.Assert(Enum.IsDefined(typeof(ConnectedDeviceType), joy.ConnectedDevice));

                //Debug.Assert(joy.EventsBinding != null);
                //EventsBindingList.List.Contains(joy.EventsBinding);
            }

            foreach (var pjoy in new ParallelJoystick [] { ParallelJoystick1, ParallelJoystick2 })
            {
                //Debug.Assert(pjoy.EventsBinding != null);
                EventsBindingList.List.Contains(pjoy.EventsBinding);
            }

            //var maps = new EventsBinding[] 
            //{ 
            //    Joyport1.EventsBinding, Joyport2.EventsBinding, 
            //    ParallelJoystick1.EventsBinding, ParallelJoystick2.EventsBinding, 
            //    Input.CDTVFrontPanelAndPilotEventsBinding, Input.KeyboardEventsBinding
            //};

            //Debug.Assert(maps.Where(m => m.DisplayName != "").All(m => m.Bindings.Count() == 0));
            //Debug.Assert(maps.Where(m => m.DisplayName == "").All(m => m.Bindings.Count() != 0));

            //maps = (from map in maps
            //        where map.DisplayName != ""
            //       select map).ToArray();

            //Debug.Assert(maps.All(m => EventsBindingList.List.Any(b => b.DisplayName ==
            //    m.DisplayName)));
            //Debug.Assert(maps.Unique());

            Debug.Assert(((Joyport1.ConnectedDevice == ConnectedDeviceType.Mouse) ? 1 : 0) + 
                ((Joyport2.ConnectedDevice == ConnectedDeviceType.Mouse) ? 1 : 0) <= 1);
            Debug.Assert(((Joyport1.ConnectedDevice == ConnectedDeviceType.LightPen) ? 1 : 0) +
                ((Joyport2.ConnectedDevice == ConnectedDeviceType.LightPen) ? 1 : 0) <= 1);

            KnownRom rom = KnownRoms.Roms.FirstOrDefault(r => r.SHA1 == Memory.Kickstart.SHA1);
            if (rom != null)
            {
                if (rom.Requirements == RomRequirements.A4000_Ramsey_Gary_IDE)
                {
                    Debug.Assert(Chipset.IDE == IDEType.IDE_4000);
                    Debug.Assert(Chipset.Ramsey == RamseyType.A4000);
                }
            }

            if (Chipset.CDTVCD)
                Debug.Assert(Chipset.DMAC == DMACType.CDTV);

            Debug.Assert(Chipset.CDTVMemoryCard != CDTVMemoryCardType.Card256KB);

            if (Chipset.BlitterImmediate)
                Debug.Assert(CPU.Speed != CPUSpeed.CycleExact);
            if (CPU.Speed == CPUSpeed.CycleExact)
                Debug.Assert(!Chipset.BlitterImmediate);

            if (Display.FilterName != "")
                Debug.Assert(Filters.List.Any(f => f.Name == Display.FilterName));

            Debug.Assert(NumberExtensions.InRange(Audio.Volume, 0, 100));
            Debug.Assert(NumberExtensions.InRange(Display.RenderEveryNFrame, 1, 50));

            Debug.Assert(NumberExtensions.InRange(Display.Contrast, -1, 1));
            Debug.Assert(NumberExtensions.InRange(Display.Brightness, -1, 1));
            Debug.Assert(NumberExtensions.InRange(Display.Gamma, 0, 2));

            Debug.Assert(NumberExtensions.InRange(CPU.AdjustHostCPULoad, 1, 100));

            if (Display.Window.Scaling == WindowScalingMode.ResizeAmigaDisplayToWindow)
                Debug.Assert(Display.Window.ScaleFactor == ScaleFactor.Unused);
            if (Display.Window.Scaling == WindowScalingMode.ResizeWindowToAmigaDisplay)
                Debug.Assert(Display.Window.ScaleFactor != ScaleFactor.Unused);
            if (Display.Window.Scaling == WindowScalingMode.Scale)
                Debug.Assert(Display.Window.ScaleFactor != ScaleFactor.Unused);

            if (Display.Fullscreen.Scaling == FullscreenScalingMode.ResizeAmigaDisplayToScreen)
                Debug.Assert(Display.Fullscreen.ScaleFactor == ScaleFactor.Unused);
            if (Display.Fullscreen.Scaling == FullscreenScalingMode.Scale)
                Debug.Assert(Display.Fullscreen.ScaleFactor != ScaleFactor.Unused);

            Debug.Assert(Audio.Latency / 1000.0 > 1 / (double)Audio.SamplingRate);
        }

        /// <summary>
        /// Try to bind to default or already loaded bindings.
        /// </summary>
        /// <param name="a_events"></param>
        /// <returns></returns>
        public static EventsBinding ResolveBindings(EventsBinding a_event_bindings)
        {
            var d = EventsBindingList.List.FirstOrDefault(b => b.Guid == a_event_bindings.Guid);

            if (d != null)
                return d;

            return a_event_bindings;
        }
    }
}
